<?php

function pt_recursive_implode ($array) {
  if (!is_array($array)) return $array;
  foreach ($array as $key => $value) $array[$key] = pt_recursive_implode($value);
  return implode($array);
}

function pt_find_tag ($template, $initial) {
  $result = strpos($template, "<!-- ##", $initial);
  if ($result === false) return null;
  return (int) $result;
}

function pt_check_tag ($template, $initial, $depth) {
  $endtag = str_repeat('#', $depth);
  $endpos = strpos($template, "<!-- ###$endtag -->", $initial);
  if ($endpos === false) return false;
  $taglen = ($endpos + 12 + $depth) - $initial;
  if ($taglen == 0) return false;
  $startpos = strpos($template, " -->", $initial);
  if ($startpos >= $endpos) return false;
  $startpos += 4;
  $paramlist = substr($template, $initial + 7, $startpos - $initial - 11);
  $contents = substr($template, $startpos, $endpos - $startpos);
  $mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
  $ppos = strspn($paramlist, $mask);
  if ($ppos == 0) return false;
  $name = substr($paramlist, 0, $ppos);
  if ((ord($name[0]) >= 48) and (ord($name[0]) <= 57)) return false;
  $ppos += strcspn($paramlist, $mask, $ppos);
  $plen = strlen($paramlist);
  $params = array();
  $paramcount = 0;
  while ($ppos < $plen) {
    $next = strspn($paramlist, $mask, $ppos);
    if ($next == 0) break;
    $params[++ $paramcount] = substr($paramlist, $ppos, $next);
    if ((ord($params[$paramcount][0]) >= 48) and (ord($params[$paramcount][0]) <= 57)) return false;
    $ppos += $next;
    $ppos += strcspn($paramlist, $mask, $ppos);
  }
  if ($paramcount > 1)
    for ($p1 = 1, $p2 = 2; $p1 < $paramcount; $p2 = 1 + (($p2 == $paramcount) ? (++ $p1) : $p2))
      if (strpos($params[$p2], $params[$p1]) === 0)
        return false;
  return array($name, $taglen, $params, $contents);
}

function pt_parse_tag ($contents, $params, $replace, $header, $footer, $parse_callbacks, $depth, $data) {
  if (!is_string($contents)) $contents = (string) $contents;
  if ($params === null) return array($header, $contents, $footer);
  if (is_bool($params))
    return ((bool) $params) ? pt_parse_template($contents, $parse_callbacks, $depth + 1, $data) : "";
  if (is_int($params)) {
    if ($params <= 0) return "";
    if ($params == 1) return pt_parse_template($contents, $parse_callbacks, $depth + 1, $data);
    $recurse = (pt_find_tag($contents, 0) !== null);
    if ($recurse) {
      $result = array();
      for ($pos = 0; $pos < $params; $pos ++)
        $result[$pos] = pt_parse_template($contents, $parse_callbacks, $depth + 1, $data);
      return $result;
    } else return (((int) $params) > 0) ? array_fill(0, (int) $params, $contents) : "";
  }
  if (is_string($params))
    return pt_parse_template($params, $parse_callbacks, $depth + 1, $data);
  if (is_array($params)) {
    if (isset($params[0])) {
      if (!is_int($params[0])) return "";
      if ($params[0] <= 0) return "";
      $result = array_fill(1, $params[0], "");
      for ($index = 1; $index <= $params[0]; $index ++)
        if (isset($params[$index]))
          $result[$index] = pt_parse_tag($contents, $params[$index], $replace, $header, $footer,
                                         $parse_callbacks, $depth, $data);
      return $result;
    } else {
      $contents = pt_parse_template($contents, $parse_callbacks, $depth + 1, $data);
      foreach ($replace as $pos => $name)
        if (isset($params[$name]))
          $contents = str_replace("$$" . $name, $params[$name], $contents);
        else if (isset($params[$pos]))
          $contents = str_replace("$$" . $name, $params[$pos], $contents);
      return $contents;
    }
  }
  return array($header, $contents, $footer);
}

function pt_parse_template ($template, $parse_callbacks, $depth, $data) {
  $result = array();
  $next = 0;
  $tpos = 0;
  $tlen = strlen($template);
  while (($tpos < $tlen) and (($nexttag = pt_find_tag($template, $tpos)) !== null)) {
    if ($nexttag > $tpos)
      $result[$next ++] = substr($template, $tpos, $nexttag - $tpos);
    $tag_data = pt_check_tag($template, $nexttag, $depth);
    if ($tag_data === false) {
      $result[$next ++] = substr($template, $nexttag, $tlen - $nexttag);
      $tpos = $tlen;
      continue;
    }
    $tpos = $nexttag + $tag_data[1];
    $theader = substr($template, $nexttag, strpos($template, "-->", $nexttag) + 3 - $nexttag);
    $tfooter = substr($template, $tpos - $depth - 12, $depth + 12);
    if (is_callable($parse_callbacks)) {
      $result[$next ++] = is_callable($parse_callbacks) ?
        pt_parse_tag($tag_data[3], call_user_func_array($parse_callbacks, array_merge(array($tag_data[0]), $data)),
                     $tag_data[2], $theader, $tfooter, $parse_callbacks, $depth, $data) :
        $theader . $tag_data[3] . $tfooter;
    } else if (is_array($parse_callbacks)) {
      $callback = @$parse_callbacks[$tag_data[0]];
      $result[$next ++] = is_callable($callback) ?
        pt_parse_tag($tag_data[3], call_user_func_array($callback, array_merge($data)), $tag_data[2], $theader, $tfooter,
                     $parse_callbacks, $depth, $data) :
        $theader . $tag_data[3] . $tfooter;
    } else
      $result[$next ++] = $theader . $tag_data[3] . $tfooter;
  }
  if ($tpos < $tlen)
    $result [$next ++] = substr($template, $tpos, $tlen - $tpos);
  return pt_recursive_implode($result);
}

function parse_template ($template, $parse_callbacks) {
  if (!is_string($template)) $template = (string) $template;
  if (!(is_callable($parse_callbacks) or is_array($parse_callbacks))) return $template;
  $data = func_get_args();
  array_splice($data, 0, 2);
  return pt_parse_template($template, $parse_callbacks, 0, $data);
}

?>